package com.google.code.UtopiaXml;

import static com.google.code.UtopiaXml.CodeBuilder.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedSet;

/**
 * Helper class for generatong classes or enums. Supports adding Properties,
 * imports and custom code generation.
 *
 * @author Michael Simacek
 */
public class ClassBuilder {

    private String className;
    private String pkgName;
    private CodeBuilder content;
    private boolean enumeration;
    private Set<Property> properties = new LinkedHashSet<>();
    private boolean generateToString = true;

    /**
     * Creates empty class in given package
     *
     * @param className name of the class
     * @param pkgName package name. Empty String for deafult package
     */
    public ClassBuilder(String className, String pkgName) {
        this.enumeration = false;
        this.className = className;
        this.pkgName = pkgName;
        content = new CodeBuilder(1);
    }

    /**
     * Class body is encapsulated in a CodeBuilder that can be returned by this
     * function to customize it
     *
     * @return CodeBuilder to insert code into class
     */
    public CodeBuilder getClassContentBuilder() {
        return content;
    }

    /**
     * Sets this class to be an enum. It is declared with the keyword enum
     * instead of class, but the rest of the functionality is unaffected
     */
    public void setEnum() {
        enumeration = true;
    }

    /**
     * Adds an instance of Property to this class. It is then used to generate
     * declaration and accessors
     *
     * @param prop Property to be added
     */
    public void addProperty(Property prop) {
        properties.add(prop);
    }

    /**
     * Adds properties to this class. They're then used to generate declarations
     * and accessors
     *
     * @param props Collections of properties to be added
     */
    public void addProperties(Collection<Property> props) {
        properties.addAll(props);
    }

    /**
     * Returns complete source code of the class in a String
     *
     * @return Resulting generated class code
     */
    @Override
    public String toString() {
        StringBuilder classBuilder = new StringBuilder();

        classBuilder.append("public ");
        if (enumeration) {
            classBuilder.append("enum ");
        } else {
            classBuilder.append("class ");
        }
        classBuilder.append(className).append(" {").append(NEWLINE).append(NEWLINE);

        for (Property prop : properties) {
            prop.generateDeclaration(content);
        }
        if (!properties.isEmpty()) {
            content.newLine();
        }
        for (Property prop : properties) {
            prop.generateAccessors(content);
        }

        if (generateToString) {
            content.appendLine("@Override");
            content.appendLine("public String toString() {");
            content.appendLine("StringBuilder sb = new StringBuilder(\"{\");");
            int counter = 0;
            for (Property prop : properties) {
                content.appendLine("sb.append(\"" + prop.getName() + " : \");");
                content.appendLine("sb.append(" + prop.getName() + ");");
                if (++counter < properties.size()) {
                    content.appendLine("sb.append(\", \");");
                }
            }
            content.appendLine("sb.append(\"}\");");
            content.appendLine("return sb.toString();");
            content.appendLine("}");
        }

        classBuilder.append(content.toString());

        classBuilder.append("}").append(NEWLINE);

        StringBuilder importBuilder = new StringBuilder();
        SortedSet<String> imports = content.getImports();
        for (String imp : imports) {
            importBuilder.append("import ").append(imp).append(";").append(NEWLINE);
        }
        if (!imports.isEmpty()) {
            importBuilder.append(NEWLINE);
        }

        StringBuilder packageBuilder = new StringBuilder();
        if (pkgName != null && !pkgName.equals("")) {
            packageBuilder.append("package ").append(pkgName).append(";").append(NEWLINE);
        }
        return packageBuilder.toString() + importBuilder.toString() + classBuilder.toString();
    }

    /**
     * Writes the class code into a corresponding file (.java). Existing files
     * are overwriten without warning.
     *
     * @param outDirectory output directory for source file
     * @throws IOException when the outDirectory doesn't exist or is not
     * writable
     */
    public void writeToFile(File outDirectory) throws IOException {
        if (!outDirectory.exists()) {
            throw new IllegalArgumentException("outDirectory does not exist");
        }
        if (!outDirectory.isDirectory()) {
            throw new IllegalArgumentException("outDirectory is not a directory");
        }
        String filePath = outDirectory.getPath() + File.separator + className + ".java";
        File out = new File(filePath);
        if (!out.exists()) {
            if (!out.createNewFile()) {
                throw new IOException("Cannot create file");
            }
        }
        try (FileWriter fw = new FileWriter(out);
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(toString());
        }
    }
}
